﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Linq;
using System.Reflection;
using System.IO;
using System.Globalization;

namespace DataObject.Core
{
    public class AccessorBase<TEntity, TContext>
        where TEntity : class, new()
        where TContext : DataContext, new()
    {
        private TContext m_context = null;

        #region protected methods

        /// <summary>
        /// It provides the default policy to handle the corrency conflict
        /// </summary>
        /// <param name="context">Data Context</param>
        protected virtual void SubmitChanges(TContext context)
        {
            try
            {
                context.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException)
            {
                context.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
                context.SubmitChanges();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region Query Method (Dynamic Query)

        public IList<TEntity> Where(string predicate, params object[] values)
        {
            InitDataContext();
            return m_context.GetTable<TEntity>().Where(predicate, values).ToList<TEntity>();
        }

        public IList<TEntity> OrderBy(string ordering, params object[] values)
        {
            InitDataContext();
            return m_context.GetTable<TEntity>().OrderBy(ordering, values).ToList<TEntity>();
        }

        public IList<TEntity> Take(int count)
        {
            InitDataContext();
            return m_context.GetTable<TEntity>().Take<TEntity>(count).ToList<TEntity>();
        }

        public IQueryable Select(string selector, params object[] values)
        {
            InitDataContext();
            return m_context.GetTable<TEntity>().Select(selector, values);
        }

        #endregion

        #region Query Methods (Lambda Expression)

        public IList<TEntity> Where(Func<TEntity, bool> predicate)
        {
            InitDataContext();
            return m_context.GetTable<TEntity>().Where(predicate).ToList<TEntity>();
        }

        #endregion

        #region Insert Methods

        public void Add(IList<TEntity> entities)
        {
            InitDataContext();
            m_context.GetTable<TEntity>().InsertAllOnSubmit(entities);

            SubmitChanges(m_context);
        }

        public void Add(TEntity entity)
        {
            InitDataContext();
            m_context.GetTable<TEntity>().InsertOnSubmit(entity);

            SubmitChanges(m_context);
        }

        #endregion

        #region update methods

        /// <summary>
        /// Update the entity which was passed
        /// </summary>
        /// <param name="originalEntity">It must be unchanged entity in another data context</param>        
        /// <param name="update">It is Action<T>delegate, it can accept Lambda Expression.</param>        
        public void Update(TEntity originalEntity, Action<TEntity> update)
        {
            Update(originalEntity, update, false);
        }

        /// <summary>
        /// Update the entity which was passed
        /// The changedEntity cann't have the relationship between the entities
        /// </summary>
        /// <param name="originalEntity">It must be unchanged entity in another data context</param>        
        /// <param name="update">It is Action<T>delegate, it can accept Lambda Expression.</param> 
        /// <param name="hasRelationship">Has relationship between the entities</param>
        public void Update(TEntity originalEntity, Action<TEntity> update, bool hasRelationship)
        {
            InitDataContext();
            //StreamWriter writer = new StreamWriter(@"c:\linq_log.txt", true);
            //m_context.Log = writer;

            try
            {
                if (hasRelationship)
                {
                    //Remove the relationship between the entitis
                    Detach(originalEntity);
                }

                m_context.GetTable<TEntity>().Attach(originalEntity);

                update(originalEntity);

                SubmitChanges(m_context);
            }
            catch (InvalidCastException ex)
            {
                throw ex;
            }
            catch (NotSupportedException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            //finally
            //{
            //    writer.Close();
            //}
        }

        //Timestamp properties cann't be Guid type;
        /// <summary>
        /// Update the entity according to the passed entity.
        /// If isModified is true, the entity must have timestamp properties(means isVersion attribute on the Mapping is true).
        /// If false, the entity's properties must set the UpdateCheck attribute to UpdateCheck.Never on the Mapping (There are some mistakes still)
        /// The changedEntity cann't have the relationship between the entities
        /// </summary>
        /// <param name="changedEntity">It shoulde be changed in another data context</param>        
        /// <param name="isModified">It indicates the entity should be considered dirty and forces the context to add the entity to the list of changed objects.</param>        
        public void Update(TEntity changedEntity, bool isModified)
        {
            Update(changedEntity, isModified, false);
        }

        //Timestamp properties cann't be Guid type;
        /// <summary>
        /// Update the entity according to the passed entity.
        /// If isModified is true, the entity must have timestamp properties(means isVersion attribute on the Mapping is true).
        /// If false, the entity's properties must set the UpdateCheck attribute to UpdateCheck.Never on the Mapping (There are some mistakes still)
        /// </summary>
        /// <param name="changedEntity">It shoulde be changed in another data context</param>        
        /// <param name="isModified">It indicates the entity should be considered dirty and forces the context to add the entity to the list of changed objects.</param>
        /// <param name="hasRelationship">Has Relationship between the entitis</param>
        public void Update(TEntity changedEntity, bool isModified, bool hasRelationship)
        {
            InitDataContext();

            //StreamWriter writer = new StreamWriter(@"c:\linq_log.txt",true);
            //m_context.Log = writer;


            try
            {
                if (hasRelationship)
                {
                    //Remove the relationship between the entities
                    Detach(changedEntity);
                }

                m_context.GetTable<TEntity>().Attach(changedEntity, isModified);
                SubmitChanges(m_context);
            }
            catch (InvalidCastException ex)
            {
                throw ex;
            }
            catch (NotSupportedException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            //finally
            //{
            //    writer.Close();
            //}
        }

        public void UpdateWithTimeStamp(TEntity changedEntity)
        {
            Update(changedEntity, true);
        }

        public void UpdateWithNoCheck(TEntity changedEntity)
        {
            Update(changedEntity, false);
        }

        /// <summary>
        /// Update the orginalEntity with changedEntity then updated to Database;
        /// </summary>
        /// <param name="changedEntity">Changed Entity which data context(if has) is different from the orginalEntity's.</param>
        /// <param name="originalEntity">Original Entity which is in a cache or get by a new Data context</param>
        public void Update(TEntity changedEntity, TEntity originalEntity)
        {
            InitDataContext();

            try
            {
                m_context.GetTable<TEntity>().Attach(changedEntity, originalEntity);
                SubmitChanges(m_context);
            }
            catch (InvalidCastException ex)
            {
                throw ex;
            }
            catch (NotSupportedException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region delete methods

        public void DeleteAll()
        {
            InitDataContext();
            IList<TEntity> entities = m_context.GetTable<TEntity>().ToList<TEntity>();
            m_context.GetTable<TEntity>().DeleteAllOnSubmit(entities);

            SubmitChanges(m_context);
        }

        /// <summary>
        /// Delete the entities according to lambda experssion condition;
        /// Don't need attach the entities due to all operations are in the same data context;
        /// </summary>
        /// <param name="predicate">Lambda expression</param>
        public void Delete(Func<TEntity, bool> predicate)
        {
            InitDataContext();
            IList<TEntity> entities = m_context.GetTable<TEntity>().Where(predicate).ToList<TEntity>();
            m_context.GetTable<TEntity>().DeleteAllOnSubmit(entities);
            SubmitChanges(m_context);
        }

        /// <summary>
        /// Delete the entities according to where clause;
        /// Don't need attach the entities due to all operations are in the same data context;
        /// </summary>
        /// <param name="predicate">where clause</param>
        /// <param name="values">the value of parameter</param>
        public void Delete(string predicate, params object[] values)
        {
            InitDataContext();
            IList<TEntity> entities = m_context.GetTable<TEntity>().Where(predicate, values).ToList<TEntity>();
            m_context.GetTable<TEntity>().DeleteAllOnSubmit(entities);
            SubmitChanges(m_context);
        }

        /// <summary>
        /// Delete the entites from another data context
        /// It cann't have the relationship between the entities;
        /// </summary>
        /// <param name="entities">They cann't own the relationship with other entity, otherwise thrown NotSupportedException</param>
        public void Delete(IList<TEntity> entities)
        {
            Delete(entities, false);
        }

        /// <summary>
        /// Delete the entites from another data context
        /// It should attach them;
        /// </summary>
        /// <param name="entities">They cann't own the relationship with other entity, otherwise thrown NotSupportedException</param>
        /// <param name="hasRelationship">Has relationship between the entities</param>
        public void Delete(IList<TEntity> entities, bool hasRelationship)
        {
            InitDataContext();
            try
            {
                if (hasRelationship)
                {
                    //Remove the relationship
                    foreach (TEntity entity in entities)
                    {
                        Detach(entity);
                    }
                }

                m_context.GetTable<TEntity>().AttachAll(entities);
                m_context.GetTable<TEntity>().DeleteAllOnSubmit(entities);

                SubmitChanges(m_context);
            }
            catch (InvalidOperationException ex)
            {
                throw ex;
            }
            catch (NotSupportedException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Delete the entity from another data context
        /// It cann't has the relationship between the entities;
        /// </summary>
        /// <param name="entity">It cann't own the relationship with other entity, otherwise thrown NotSupportedException</param>
        public void Delete(TEntity entity)
        {
            Delete(entity, false);
        }

        /// <summary>
        /// Delete the entity from another data context
        /// It should attach the entity;
        /// </summary>
        /// <param name="entity">It cann't own the relationship with other entity, otherwise thrown NotSupportedException</param>
        /// <param name="hasRelationship">Has relationship between the entities</param>
        public void Delete(TEntity entity, bool hasRelationship)
        {
            InitDataContext();
            try
            {
                if (hasRelationship)
                {
                    //Remove the relationship between the entities;
                    Detach(entity);
                }

                m_context.GetTable<TEntity>().Attach(entity);
                m_context.GetTable<TEntity>().DeleteOnSubmit(entity);
                SubmitChanges(m_context);
            }
            catch (InvalidOperationException ex)
            {
                throw ex;
            }
            catch (NotSupportedException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        #endregion

        #region private methods

        private TContext CreateContext()
        {
            return Activator.CreateInstance<TContext>() as TContext;
        }

        private void InitDataContext()
        {
            m_context = CreateContext();
        }

        private void Detach(TEntity entity)
        {
            foreach (FieldInfo fi in entity.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (fi.FieldType.ToString().Contains("EntityRef"))
                {
                    var value = fi.GetValue(entity);
                    if (value != null)
                    {
                        fi.SetValue(entity, null);
                    }
                }
                if (fi.FieldType.ToString().Contains("EntitySet"))
                {
                    var value = fi.GetValue(entity);
                    if (value != null)
                    {
                        MethodInfo mi = value.GetType().GetMethod("Clear");
                        if (mi != null)
                        {
                            mi.Invoke(value, null);
                        }

                        fi.SetValue(entity, value);
                    }
                }
            }
        }

        #endregion

        public virtual void UpdateOrders(string TableName, string WhereClause, string Whereclause1, string WhereClause2, int NewIndex)
        {
            int MaxRecode = 100;
            StringBuilder str = new StringBuilder();
            str.AppendLine("DECLARE @NewIndex INT");
            str.AppendLine("DECLARE @MaxOrder INT");//DisplayOrder Max so voi dieu kien Whereclause1
            str.AppendLine("DECLARE @OldIndex INT");//Vi tri cu the deu kien Whereclause
            //Xac dinh Max so voi dieu kien Whereclause1
            str.AppendLine("SELECT @MaxOrder = ISNULL(MAX(DisplayOrder),0) FROM " + TableName + " WHERE DisplayOrder <>" + MaxRecode + WhereClause + WhereClause2);
            //Xac dinh vi tri cu so voi dieu kien Whereclause1
            str.AppendLine("SELECT @OldIndex = DisplayOrder FROM " + TableName + " WHERE " + Whereclause1);

            str.AppendLine("IF(@OldIndex > " + (MaxRecode - 1) + ")");
            str.AppendLine("SET @OldIndex=" + MaxRecode);

            str.AppendLine(" IF(@OldIndex>" + NewIndex + ")");
            str.AppendLine(" UPDATE  " + TableName + " SET DisplayOrder = DisplayOrder+1 ");
            str.AppendLine(" WHERE DisplayOrder>=" + NewIndex + " AND DisplayOrder < @OldIndex " + WhereClause);

            str.AppendLine(" ELSE    ");
            str.AppendLine(" BEGIN    ");
            str.AppendLine(" IF(" + NewIndex + ">" + (MaxRecode - 1) + ")    ");
            str.AppendLine("    UPDATE  " + TableName + " SET DisplayOrder = DisplayOrder-1    ");
            str.AppendLine("    WHERE DisplayOrder > @OldIndex AND DisplayOrder <= " + (MaxRecode - 1) + WhereClause);
            str.AppendLine(" ELSE");
            str.AppendLine("    UPDATE  " + TableName + " SET DisplayOrder = DisplayOrder-1    ");
            str.AppendLine("    WHERE DisplayOrder > @OldIndex AND DisplayOrder<=" + NewIndex + " " + WhereClause);
            str.AppendLine(" END");

            str.AppendLine("SELECT @MaxOrder = ISNULL(MAX(DisplayOrder),0) FROM " + TableName + " WHERE DisplayOrder <>" + MaxRecode + WhereClause + WhereClause2);
            str.AppendLine(" IF(" + NewIndex + ">@MaxOrder) ");
            //str.AppendLine("    SET @NewIndex=@MaxOrder+1");
            str.AppendLine(" UPDATE " + TableName + " SET DisplayOrder =(@MaxOrder+1)");
            str.AppendLine(" WHERE " + Whereclause1);
            str.AppendLine(" ELSE ");
            str.AppendLine(" UPDATE " + TableName + " SET DisplayOrder =" + NewIndex);
            str.AppendLine(" WHERE " + Whereclause1);
            using (TContext db = new TContext())
            {
                db.ExecuteCommand(str.ToString());
            }
        }

    }
    public class CustomProvider : IFormatProvider
    {
        private string cultureName;

        public CustomProvider(string cultureName)
        {
            this.cultureName = cultureName;
        }

        public object GetFormat(Type formatType)
        {
            if (formatType == typeof(DateTimeFormatInfo))
            {
                Console.Write("(CustomProvider retrieved.) ");
                return new CultureInfo(cultureName).GetFormat(formatType);
            }
            else
            {
                return null;
            }
        }
    }

}
